﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using KAImage.Algorithms;

namespace IPCPhotoEditor
{
    public class HistogramBackground : IDisposable
    {       
        private List<Bitmap> _gridLines = new List<Bitmap>();

        private List<Bitmap> _histograms= new List<Bitmap>();

        private Bitmap _baseline = null;

        /// <summary>
        /// 整个图层的宽度
        /// </summary>
        private readonly int W = 510;

        /// <summary>
        /// 整个图层的高度
        /// </summary>
        private readonly int H = 510;

        /// <summary>
        /// 需要绘制柱形图的RGB模式
        /// </summary>
        private int _drawMode = 0;
        public int DrawMode
        {
            get { return _drawMode; }
            set
            { 
                _drawMode = value;
            }
        }

        private Color _bgColor = Color.FromArgb(0, 255, 255, 255);

         private Bitmap _sourceImage = null;

         private ImageHistograms histogram = new ImageHistograms();

         public Bitmap this[int baseLine, int gridLineIndex, int channel] 
         {
            get 
            {
                if (baseLine < 0 || baseLine > 1)
                {
                    //throw new Exception();
                    return null;
                }
                if (gridLineIndex <= 0 || gridLineIndex > 2)
                {
                    //throw new Exception();
                    return null;
                }
                if (channel < 0 || channel > 4)
                {
                    //throw new Exception();
                    return null;
                }
                if (baseLine == 0 && channel == 0)
                {
                    Bitmap cloneImage = _gridLines[gridLineIndex - 1].Clone() as Bitmap;
                    return cloneImage;
                }
                Bitmap[] SrcImg = null;
                if (baseLine == 0)
                {
                    SrcImg = new Bitmap[2];                
                    SrcImg[0] = _histograms[channel - 1];
                    SrcImg[1] = _gridLines[gridLineIndex - 1];
                }               
                else if (channel == 0)
                {
                    SrcImg = new Bitmap[2];
                    SrcImg[0] = _gridLines[gridLineIndex - 1];
                    SrcImg[1] = _baseline;                   
                }
                else
                {
                    SrcImg = new Bitmap[3];
                    SrcImg[0] = _histograms[channel - 1];
                    SrcImg[1] = _gridLines[gridLineIndex - 1];
                    SrcImg[2] = _baseline;
                }
                return BlendImage(SrcImg);
            }
        }

        public HistogramBackground(Bitmap bmp)
        {
            _sourceImage = bmp.Clone() as Bitmap;
            OnGeneratingImages();
        }

        private void OnGeneratingImages()
        {
            OnGeneratingHistogram();
            OnGeneratingGridLinesImage();
            OnGeneratingBaseLine();
        }

        private void OnGeneratingBaseLine()
        {
             _baseline = new Bitmap(W, H, PixelFormat.Format32bppArgb);
             using (Graphics g = Graphics.FromImage(_baseline))
             {
                 Pen gridLinePen = new Pen(Color.FromArgb(255, 180, 180, 180), 2);
                 g.Clear(_bgColor);
                 g.DrawLine(gridLinePen, 0, 508, 508, 0);
                 gridLinePen.Dispose();
                 gridLinePen = null;
             }
        }

        private void OnGeneratingHistogram()
        {
            histogram.CalculateHistograms(_sourceImage);
            _histograms.Add(OnDrawingHistogram(ColorChannel.RGB, Color.FromArgb(255, 220, 220, 220)));
            _histograms.Add(OnDrawingHistogram(ColorChannel.R, Color.FromArgb(128, 255, 0, 0)));
            _histograms.Add(OnDrawingHistogram(ColorChannel.G, Color.FromArgb(128, 0, 255, 0)));
            _histograms.Add(OnDrawingHistogram(ColorChannel.B, Color.FromArgb(128, 0, 0, 255)));        
        }

        private Bitmap OnDrawingHistogram(ColorChannel channel, Color color)
        {
            int[] data = null;
            int maximum = 0;
            switch (channel)
            {
                case ColorChannel.R:
                    data = histogram.hR;
                    maximum = histogram.nChannelMedianPixel;
                    break;
                case ColorChannel.G:
                    data = histogram.hG;
                    maximum = histogram.nChannelMedianPixel;
                    break;
                case ColorChannel.B:
                    data = histogram.hB;
                    maximum = histogram.nChannelMedianPixel;
                    break;
                case ColorChannel.RGB:
                    data = histogram.hRGB;
                    maximum = histogram.nMedianPixel;
                    break;
                default:
                    return null;
            }
            Bitmap bm = new Bitmap(W, H, PixelFormat.Format32bppArgb);
            using (Graphics g = Graphics.FromImage(bm))
            {
                g.Clear(_bgColor);
                using (Pen linePen = new Pen(color, 2))
                {
                    //绘制直方图
                    double temp = 0;
                    for (int i = 0; i < 256; i++)
                    {
                        temp = (double)H * data[i] / maximum;
                        g.DrawLine(linePen, 2 * i, H, 2 * i, H - (int)temp);
                    }
                }
            }        
            return bm;       
        }

        public void OnGeneratingGridLinesImage()
        {
            List<List<Point>> listLines = GetGridLinePoints1();
            Pen gridLinePen = new Pen(Color.FromArgb(255, 180, 180, 180), 2);
            _gridLines.Add(new Bitmap(W, H, PixelFormat.Format32bppArgb));
            using (Graphics g = Graphics.FromImage(_gridLines[0]))
            {
                g.Clear(_bgColor);
                foreach (List<Point> list in listLines)
                {
                    g.DrawLines(gridLinePen, list.ToArray());
                }              
                g.Dispose();
            }

            List<List<Point>> listLines2 = GetGridLinePoints2();
            _gridLines.Add(new Bitmap(W, H, PixelFormat.Format32bppArgb));
            using (Graphics g2 = Graphics.FromImage(_gridLines[1]))
            {
                g2.Clear(_bgColor);
                foreach (List<Point> list in listLines2)
                {
                    g2.DrawLines(gridLinePen, list.ToArray());
                }
                g2.Dispose();
            }
            gridLinePen.Dispose();
            gridLinePen = null;
        }

        public void Dispose()
        {
            for (int i = 0; i < _gridLines.Count; i++)
            {
                if (_gridLines[i] != null)
                {
                    _gridLines[i].Dispose();
                    _gridLines[i] = null;
                }
            }
            for (int i = 0; i < _histograms.Count; i++)
            {
                if (_histograms[i] != null)
                {
                    _histograms[i].Dispose();
                    _histograms[i] = null;
                }
            }
            if (_baseline != null)
            {
                _baseline.Dispose();
                _baseline = null;
            }
            if (_sourceImage != null)
            {
                _sourceImage.Dispose();
                _sourceImage = null;
            }
        }
  
        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = true)]
        unsafe internal static extern void CopyMemory(byte* Dest, byte* src, int Length);

        private void BlendImageGdiP(Bitmap[] SrcImg, ref Bitmap DestImg)
        {
            Graphics G = Graphics.FromImage(DestImg);
            for (int X = 0; X < SrcImg.Length; X++)
            {
                G.DrawImage(SrcImg[X], new Rectangle(0, 0, 800, 600), new Rectangle(0, 0, 800, 600), GraphicsUnit.Pixel);
            }
            G.Dispose();
        }

        private Bitmap BlendImage(Bitmap[] SrcImg)
        {
            if (SrcImg == null)
            {
                return null;
            }
            foreach (Bitmap bm in SrcImg)
            {
                if (bm == null)
                {
                    return null;
                }
            }

            int Y, Z, Value, Alpha;
            byte Red, Green, Blue;
            Bitmap DestImg = new Bitmap(W, H, PixelFormat.Format32bppArgb);
            unsafe
            {
                byte* SrcData, DestData;
                BitmapData DestBmpData = DestImg.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
                for (Z = 0; Z < SrcImg.Length; Z++)
                {
                    BitmapData SrcBmpData = SrcImg[Z].LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                    SrcData = (byte*)SrcBmpData.Scan0;
                    DestData = (byte*)DestBmpData.Scan0;
                    if (Z == 0)
                    {
                        CopyMemory(DestData, SrcData, W * H * 4);
                        SrcImg[Z].UnlockBits(SrcBmpData);
                        continue;
                    }                 
                    for (Y = 0; Y < W * H; Y++)
                    {
                        Alpha = *(SrcData + 3);
                        if (Alpha == 255)
                        {
                            *DestData = *SrcData;
                            *(DestData + 1) = *(SrcData + 1);
                            *(DestData + 2) = *(SrcData + 2);
                            *(DestData + 3) = *(SrcData + 3);
                        }
                        else if (Alpha != 0)
                        {
                            Blue = *DestData;
                            Value = (Blue << 8) - Blue + (*SrcData - Blue) * Alpha;
                            *DestData = (byte)(((Value >> 8) + Value + 1) >> 8);
                            Green = *(DestData + 1);
                            Value = (Green << 8) - Green + (*(SrcData + 1) - Green) * Alpha;
                            *(DestData + 1) = (byte)(((Value >> 8) + Value + 1) >> 8);
                            Red = *(DestData + 2);
                            Value = (Red << 8) - Red + (*(SrcData + 2) - Red) * Alpha;
                            *(DestData + 2) = (byte)(((Value >> 8) + Value + 1) >> 8);
                            *(DestData + 3) = (byte)(((*(DestData + 3) * (256 - (Alpha + (Alpha >> 7)))) >> 8) + Alpha);        // A1+A2-A1*A2/255的优化
                        }                       
                        SrcData += 4;
                        DestData += 4;
                    }
                    SrcImg[Z].UnlockBits(SrcBmpData);
                }

                // 如果算到最后，该像素点是透明的，设置它为白色
                DestData = (byte*)DestBmpData.Scan0;
                for (Y = 0; Y < W * H; Y++)
                {
                    Alpha = *(DestData + 3);
                    if (Alpha == 0)
                    {
                        *DestData = 255;
                        *(DestData + 1) = 255;
                        *(DestData + 2) = 255;
                        *(DestData + 3) = 255;
                    }
                    DestData += 4;
                }
               
                DestImg.UnlockBits(DestBmpData);     
            }                  
            return DestImg;  
        }

        private List<List<Point>> GetGridLinePoints1()
        {
            List<List<Point>> listLines = new List<List<Point>>();
            List<Point> list0 = new List<Point>();
            list0.Add(new Point(0, 127));
            list0.Add(new Point(510, 127));
            listLines.Add(list0);

            List<Point> list1 = new List<Point>();
            list1.Add(new Point(0, 255));
            list1.Add(new Point(510, 255));
            listLines.Add(list1);

            List<Point> list2 = new List<Point>();
            list2.Add(new Point(0, 382));
            list2.Add(new Point(510, 382));
            listLines.Add(list2);

            List<Point> listY0 = new List<Point>();
            listY0.Add(new Point(127, 0));
            listY0.Add(new Point(127, 510));
            listLines.Add(listY0);

            List<Point> listY1 = new List<Point>();
            listY1.Add(new Point(255, 0));
            listY1.Add(new Point(255, 510));
            listLines.Add(listY1);

            List<Point> listY2 = new List<Point>();
            listY2.Add(new Point(382, 0));
            listY2.Add(new Point(382, 510));
            listLines.Add(listY2);

            return listLines;
        }

        private List<List<Point>> GetGridLinePoints2()
        {
            List<List<Point>> listLines = new List<List<Point>>();
            List<Point> list0 = new List<Point>();
            list0.Add(new Point(0, 51));
            list0.Add(new Point(510, 51));
            listLines.Add(list0);

            List<Point> list1 = new List<Point>();
            list1.Add(new Point(0, 102));
            list1.Add(new Point(510, 102));
            listLines.Add(list1);

            List<Point> list2 = new List<Point>();
            list2.Add(new Point(0, 153));
            list2.Add(new Point(510, 153));
            listLines.Add(list2);

            List<Point> list3 = new List<Point>();
            list3.Add(new Point(0, 204));
            list3.Add(new Point(510, 204));
            listLines.Add(list3);

            List<Point> list4 = new List<Point>();
            list4.Add(new Point(0, 255));
            list4.Add(new Point(510, 255));
            listLines.Add(list4);

            List<Point> list5 = new List<Point>();
            list5.Add(new Point(0, 306));
            list5.Add(new Point(510, 306));
            listLines.Add(list5);

            List<Point> list6 = new List<Point>();
            list6.Add(new Point(0, 357));
            list6.Add(new Point(510, 357));
            listLines.Add(list6);

            List<Point> list7 = new List<Point>();
            list7.Add(new Point(0, 408));
            list7.Add(new Point(510, 408));
            listLines.Add(list7);

            List<Point> list8 = new List<Point>();
            list8.Add(new Point(0, 459));
            list8.Add(new Point(510, 459));
            listLines.Add(list8);

            List<Point> listY0 = new List<Point>();
            listY0.Add(new Point(51, 0));
            listY0.Add(new Point(51, 510));
            listLines.Add(listY0);

            List<Point> listY1 = new List<Point>();
            listY1.Add(new Point(102, 0));
            listY1.Add(new Point(102, 510));
            listLines.Add(listY1);

            List<Point> listY2 = new List<Point>();
            listY2.Add(new Point(153, 0));
            listY2.Add(new Point(153, 510));
            listLines.Add(listY2);

            List<Point> listY3 = new List<Point>();
            listY3.Add(new Point(204, 0));
            listY3.Add(new Point(204, 510));
            listLines.Add(listY3);

            List<Point> listY4 = new List<Point>();
            listY4.Add(new Point(255, 0));
            listY4.Add(new Point(255, 510));
            listLines.Add(listY4);

            List<Point> listY5 = new List<Point>();
            listY5.Add(new Point(306, 0));
            listY5.Add(new Point(306, 510));
            listLines.Add(listY5);

            List<Point> listY6 = new List<Point>();
            listY6.Add(new Point(357, 0));
            listY6.Add(new Point(357, 510));
            listLines.Add(listY6);

            List<Point> listY7 = new List<Point>();
            listY7.Add(new Point(408, 0));
            listY7.Add(new Point(408, 510));
            listLines.Add(listY7);

            List<Point> listY8 = new List<Point>();
            listY8.Add(new Point(459, 0));
            listY8.Add(new Point(459, 510));
            listLines.Add(listY8);
            return listLines;
        }
    }
}
